package com.file.parser;

import com.file.bo.ResponseData;
import com.file.bo.mail.TjBank;
import com.file.bo.mail.TjBankTran;
import com.file.constant.ErrorCode;
import com.file.util.JsonUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.pdfparser.PDFStreamParser;
import org.apache.pdfbox.pdfwriter.ContentStreamWriter;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import org.apache.pdfbox.util.Matrix;
import technology.tabula.*;
import technology.tabula.detectors.NurminenDetectionAlgorithm;
import technology.tabula.detectors.SpreadsheetDetectionAlgorithm;
import technology.tabula.extractors.BasicExtractionAlgorithm;

import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class TjBankPdfParser extends BasePdfParser {

    private static final int MERGE_THRESHOLD = 10;

    public ResponseData<String> parseTjBankPdfToJson(String daId, String filePath) {
        log.info("parseTjBankPdfToJson started, daId:{}", daId);
        String json = null;

        try {
            TjBank tjBank = parseTjBankPdf(filePath);
            json = JsonUtils.convertObjectToJson(tjBank);
        } catch (Exception e) {
            log.error("OnError|{}||{}|{}|{}|{}", ErrorCode.FILE_PARSE_EXCEPTION.getCode(), daId, "", "", "parseTjBankPdfToJson failed", e);
            return new ResponseData<String>(json, ErrorCode.FILE_PARSE_EXCEPTION.getCode(),
                    ErrorCode.FILE_PARSE_EXCEPTION.getMsg());
        }

        log.info("parseTjBankPdfToJson completed, daId:{}", daId);
        return new ResponseData<String>(json, ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg());
    }

    private TjBank parseTjBankPdf(String filePath) {
        String pdfHeaderText = parsePdfHeaderText(filePath);
        TjBank tjBank = parseTjBankHeader(pdfHeaderText);
        boolean hasCounterPartyName = pdfHeaderText.contains("对手户名");
        List<List<String>> rowList = parseFileToRowList(filePath, tjBank.getAccountName());
        parseListToBO(rowList, tjBank, hasCounterPartyName);

        return tjBank;
    }

    private void parseListToBO(List<List<String>> rowList, TjBank tjBank, boolean hasCounterPartyName) {
        List<TjBankTran> tjBankTrans = new ArrayList<>();
        tjBank.setTjTrans(tjBankTrans);

        for (int i = 0; i < rowList.size(); i++) {
            List<String> rows = rowList.get(i);
//            System.out.println(StringUtils.join(rows));
            if (!rows.get(0).matches("\\d+")) {
                continue;
            }
            if (hasCounterPartyName) {
                if (rows.size() == 8) {
                    TjBankTran tjBankTran = new TjBankTran();
                    tjBankTran.setSerialNumber(rows.get(0));
                    tjBankTran.setTransactionDate(rows.get(1));
                    tjBankTran.setTransactionAmount(rows.get(2));
                    tjBankTran.setBalance(rows.get(3));
                    tjBankTran.setTransactionSummary(rows.get(4));
                    tjBankTran.setCounterpartyName(rows.get(5));
                    tjBankTran.setCounterpartyAccountNo(rows.get(6));
                    tjBankTran.setRemarks(rows.get(7));
                    tjBankTrans.add(tjBankTran);

                }
            } else {

                if (rows.size() == 6) {
                    TjBankTran tjBankTran = new TjBankTran();
                    tjBankTran.setSerialNumber(rows.get(0));
                    tjBankTran.setTransactionDate(rows.get(1));
                    tjBankTran.setTransactionAmount(rows.get(2));
                    tjBankTran.setBalance(rows.get(3));
                    tjBankTran.setTransactionSummary(rows.get(4));
                    tjBankTran.setCounterpartyName("");
                    tjBankTran.setCounterpartyAccountNo("");
                    tjBankTran.setRemarks(rows.get(5));
                    tjBankTrans.add(tjBankTran);
                }
            }

        }

    }

    private List<List<String>> parseFileToRowList(String filePath, String waterMark) {
        //页面所有table的记录，每一个元素代表下面一条记录cellList
        List<List<String>> rowList = new ArrayList<>();

        // 1. 读取文件
        File pdf = new File(filePath);

        // 2. pdfbox读取PDDocument
        try (PDDocument pdfDocument = PDDocument.load(pdf)) {

            // 3. tabula新建ObjectExtractor和NurminenDetectionAlgorithm，同时准备接收表格Rectangle的结构
            ObjectExtractor objectExtractor = new ObjectExtractor(pdfDocument);
            NurminenDetectionAlgorithm detectionAlgorithm = new NurminenDetectionAlgorithm();
            Map<Integer, List<Rectangle>> detectedTables = new HashMap<>();

            // 4. 获取每页的PageIterator
            PageIterator pages = objectExtractor.extract();

            // 5. 解析每页的Rectangle(table的位置)
            while (pages.hasNext()) {
                Page page = pages.next();
                removeWaterMark(page.getPDPage(), page.getPageNumber(), waterMark);
                List<Rectangle> tablesOnPage = detectionAlgorithm.detect(page);
                if (tablesOnPage.size() > 0) {
                    detectedTables.put(page.getPageNumber(), tablesOnPage);
                } else {
                    tablesOnPage = new SpreadsheetDetectionAlgorithm().detect(page);
                    if (tablesOnPage.size() > 0) {
                        detectedTables.put(page.getPageNumber(), tablesOnPage);
                    } else {
                        // 适配部分pdf 表格位置识别失败
                        float top = findTextBottom(filePath, page.getPageNumber(), "序号");
                        if (top > 0) {
                            Rectangle rectangle = new Rectangle(top - 10, 0, 600, 700);
                            detectedTables.put(page.getPageNumber(), Collections.singletonList(rectangle));
                        }
                    }
                }

            }

            // 6.通过table位置获取表格具体内容
            BasicExtractionAlgorithm bea = new BasicExtractionAlgorithm();

            // 如果单页pdf中有多个表格，则解析出来的List<Rectangle>会有多个对象
            for (Map.Entry<Integer, List<Rectangle>> entry : detectedTables.entrySet()) {
                Page page = objectExtractor.extract(entry.getKey());
                // 默认每页只有一个表格，因此获取第0个rectangle/
                Rectangle rectangle = null; //NOSONAR
                List<Rectangle> rectangleList = entry.getValue();
                for (Rectangle r : rectangleList) {
                    double rectangleHeight = r.getHeight();
                    if (Double.compare(rectangleHeight, 0) > 0) {
                        rectangle = r;
                        break;
                    }
                }
                if (rectangle == null) {
                    throw new RuntimeException("rectangle is null.");
                }

                if (rectangle.getLeft() > 45) {
                    rectangle.setLeft(45);
                }
                if (entry.getKey() != 1) {
                    rectangle.setTop(25);
                }
                rectangle.setRight(rectangle.getRight() + 100);
                rectangle.setBottom(rectangle.getBottom() + 900);
                Page area = page.getArea(rectangle);
                List<Table> tableList = null;
                try {
                    tableList = bea.extract(area);
                } catch (Exception e) {
                    log.info("rectangle extract table fail.");
                    continue;
                }
                List<List<Cell>> pageCellList = new ArrayList<>();
                // 如果每页有多个表格，解析每一个table
                for (Table table : tableList) {
                    for (int i = 0; i < table.getRowCount(); i++) {
                        List<Cell> cellList = new ArrayList<>();//一条记录，每个单元格占一个元素
                        for (int j = 0; j < table.getColCount(); j++) {
                            RectangularTextContainer textContainer = table.getCell(i, j);
                            Cell cell = new Cell(textContainer.getText(false), textContainer.getRight(), textContainer.getBottom());
                            cellList.add(cell);
                        }
                        pageCellList.add(cellList);
                    }
                }
                rowList.addAll(mergeCells(pageCellList));
            }
            return rowList;
        } catch (
                Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static List<List<String>> mergeCells(List<List<Cell>> pageCellList) {
        List<List<String>> rowList = new ArrayList<>();

        for (int rowIndex = 0; rowIndex < pageCellList.size(); rowIndex++) {
            List<Cell> row = pageCellList.get(rowIndex);
            if (rowIndex == 0 || rowIndex == pageCellList.size() - 1) {
                List<String> rows = row.stream().map(cell -> cell.cellText).collect(Collectors.toList());
                rowList.add(rows);
                continue;
            }
            List<Cell> previousRow = pageCellList.get(rowIndex - 1);
            List<Cell> nextRow = pageCellList.get(rowIndex + 1);

            if (row.isEmpty() || StringUtils.isBlank(row.get(0).cellText)) {
                continue;
            }

            List<Cell> mergedRow = new ArrayList<>();

            for (int i = 0; i < row.size(); i++) {
                Cell currentCell = row.get(i);
                Cell previousCell = previousRow.get(i);
                float bottom = row.get(0).bottom;
                String previousToPreviousCellText = "";
                String previousCellText = "";

                // 如果存在上两行，且满足合并条件，则获取上两行单元格的内容
                if (rowIndex > 1) {
                    List<Cell> previousToPreviousRow = pageCellList.get(rowIndex - 2);
                    Cell previousToPreviousCell = previousToPreviousRow.get(i);
                    if (StringUtils.isBlank(previousToPreviousRow.get(0).cellText) && Math.abs(previousToPreviousCell.bottom - bottom) < MERGE_THRESHOLD) {
                        previousToPreviousCellText = previousToPreviousCell.cellText;
                    }
                }

                // 如果上一行满足合并条件，则将上一行单元格内容追加到当前单元格
                if (StringUtils.isBlank(previousRow.get(0).cellText) && Math.abs(previousCell.bottom - bottom) < MERGE_THRESHOLD) {
                    previousCellText = previousCell.cellText;
                }
                currentCell.cellText = previousToPreviousCellText + previousCellText + currentCell.cellText;
                Cell nextCell = nextRow.get(i);

                // 如果下一行满足合并条件，则将下一行单元格内容追加到当前单元格
                if (StringUtils.isBlank(nextRow.get(0).cellText) && Math.abs(nextCell.bottom - bottom) < MERGE_THRESHOLD) {
                    currentCell.cellText += nextCell.cellText;
                }

                // 如果存在下两行，且满足合并条件，则将下两行单元格内容追加到当前单元格
                if (rowIndex < pageCellList.size() - 2) {
                    List<Cell> nextToNextRow = pageCellList.get(rowIndex + 2);
                    Cell nextToNextCell = nextToNextRow.get(i);
                    if (StringUtils.isBlank(nextToNextRow.get(0).cellText) && Math.abs(nextToNextCell.bottom - bottom) < MERGE_THRESHOLD) {
                        currentCell.cellText += nextToNextCell.cellText;
                    }
                }

                mergedRow.add(currentCell);
            }

            List<String> stringRow = new ArrayList<>();
            for (Cell cell : mergedRow) {
                stringRow.add(cell.cellText);
            }
            rowList.add(stringRow);
        }

        return rowList;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class Cell {
        String cellText;
        float right;
        float bottom;
    }

    private TjBank parseTjBankHeader(String pdfHeaderText) {
        TjBank tjBank = new TjBank();

        String accountName = extractValue(pdfHeaderText, "户名:", "账号:");
        tjBank.setAccountName(accountName);

        String accountNumber = extractValue(pdfHeaderText, "账号:", "打印日期");
        tjBank.setAccountNumber(accountNumber);

        String printTime = extractValue(pdfHeaderText, "打印日期：", "币种:");
        tjBank.setPrintTime(printTime);

        String currency = extractValue(pdfHeaderText, "币种:", "存款类型:");
        tjBank.setCurrency(currency);

        String depositType = extractValue(pdfHeaderText, "存款类型:", "开户行:");
        tjBank.setDepositType(depositType);

        String bankBranch = extractValue(pdfHeaderText, "开户行:", "交易日期:");
        tjBank.setBankBranch(bankBranch);

        String queryStartEndDate = extractValue(pdfHeaderText, "交易日期:", "序号");
        tjBank.setQueryStartEndDate(queryStartEndDate);

        tjBank.setAccountNumber(accountNumber);


        return tjBank;
    }

    private String extractValue(String text, String startKey, String endKey) {
        if (text == null || !text.contains(startKey)) return "";

        int startIndex = text.indexOf(startKey) + startKey.length();
        int endIndex = (endKey != null && text.contains(endKey)) ? text.indexOf(endKey) : text.length();

        return text.substring(startIndex, endIndex).trim();
    }

    static float findTextBottom(String pdfPath, int pageIndex, String searchText) throws IOException {
        float[] y = new float[1];
        try (PDDocument document = PDDocument.load(new File(pdfPath))) {
            PDFTextStripper stripper = new PDFTextStripper() {
                @Override
                protected void writeString(String text, List<TextPosition> textPositions) {
                    StringBuilder sb = new StringBuilder();
                    for (TextPosition tp : textPositions) {
                        sb.append(tp.getUnicode());
                    }
                    if (sb.toString().contains(searchText)) {
                        TextPosition tp = textPositions.get(0);
                        y[0] = tp.getY();
                    }
                }
            };
            stripper.setSortByPosition(true);
            stripper.setStartPage(pageIndex);
            stripper.setEndPage(pageIndex);
            stripper.getText(document);
        }
        return y[0];
    }

    @Override
    public String parsePdfHeaderText(String filePath) {
        String pdfHeaderText = "";
        try (PDDocument pdDocument = PDDocument.load(new File(filePath))) {
            PDPage page = pdDocument.getPages().get(0);
            PDFTextStripper stripper = new PDFTextStripper() {
                @Override
                protected void processTextPosition(TextPosition text) {
                    // 只处理非水印（非倾斜）文本
                    if (!isWatermark(text)) {
                        super.processTextPosition(text);
                    }
                }

                private boolean isWatermark(TextPosition text) {
                    float angle = calculateTextAngle(text);
                    return Math.abs(angle) > 10.0f;
                }

                private float calculateTextAngle(TextPosition text) {
                    Matrix textMatrix = text.getTextMatrix();
                    float a = textMatrix.getScaleX();
                    float b = textMatrix.getShearY();
                    return (float) Math.toDegrees(Math.atan2(b, a));
                }
            };
            stripper.setSortByPosition(true);
            stripper.setStartPage(1);
            stripper.setEndPage(1);
            pdfHeaderText = stripper.getText(pdDocument);
            pdfHeaderText = pdfHeaderText.replace(System.getProperty("line.separator", "\n"), "");
        } catch (Exception e) {
            log.error("Parse error", e);
            throw new RuntimeException(e);
        }
        return pdfHeaderText;
    }

    private static void removeWaterMark(PDPage page, int pageindex, String waterMarkString) throws IOException {
        PDResources resources = page.getResources();
        // 需要的字体文件
        Map<COSName, PDFont> oldfont = new HashMap<COSName, PDFont>();
        COSName fontName = null;

        COSName xf1Name = COSName.getPDFName("Xf" + pageindex);
        PDFormXObject xobject = (PDFormXObject) resources.getXObject(xf1Name);

        boolean isWaterMarkRow = false;
        if (xobject != null) {
            PDFStreamParser parser = new PDFStreamParser(xobject.getContentStream());
            parser.parse();
            List<Object> tokens = parser.getTokens();
            for (int j = 0; j < tokens.size(); j++) {
                //创建一个object对象去接收标记
                Object next = tokens.get(j);
                //instanceof判断其左边对象是否为其右边类的实例
                if (next instanceof COSName) {
                    fontName = (COSName) next;
                    if (!oldfont.containsKey(fontName)) {
                        oldfont.put(fontName, page.getResources().getFont(fontName));
                    }
                } else if (next instanceof COSString) {
//                    System.out.println("原始 Tj 内容: " + ((COSString) next).getString());
                    COSString previous = (COSString) next;
                    if (fontName != null && !Objects.equals(fontName.getName(), "F1")) {
                        isWaterMarkRow = true;
                        fontName = oldfont.keySet().stream().filter(x -> x.getName().equals("F1")).findFirst().get();
                    }
                    if (!isWaterMarkRow) {
                        continue;
                    }
                    try (InputStream in = new ByteArrayInputStream(previous.getBytes())) {
                        StringBuilder sb = new StringBuilder();
                        while (in.available() > 0 && oldfont.get(fontName) != null) {
                            int rc = oldfont.get(fontName).readCode(in);
                            sb.append(oldfont.get(fontName).toUnicode(rc));
                        }
                        //重置COSString对象
                        String text = sb.toString();
//                        System.out.println("--Tj----" + text);
                        if (text.contains(waterMarkString)) {
                            previous.setValue(oldfont.get(fontName).encode(""));
                        }
                    }
                }
            }
            try (OutputStream out = xobject.getContentStream().createOutputStream()) {
                new ContentStreamWriter(out).writeTokens(tokens);
            } catch (IOException e) {
                log.error("OnError|{}||{}|{}|{}|{}", ErrorCode.FILE_PARSE_EXCEPTION.getCode(), "", "", "removeWaterMark failed", e);
            }

        }
    }

    public static void main(String[] args) throws IOException {
        TjBankPdfParser tjBankPdfParser = new TjBankPdfParser();
        String json = "";
        String path = "";
//        json = tjBankPdfParser.parseTjBankPdfToJson("", "D:\\data\\file\\tjBank\\天津银行交易明细-只有1页.pdf").getData();
//        System.out.println(json);
//
//        json = tjBankPdfParser.parseTjBankPdfToJson("", "D:\\data\\file\\tjBank\\天津银行交易明细-无交易对手.pdf").getData();
//        System.out.println(json);
//
//        json = tjBankPdfParser.parseTjBankPdfToJson("", "D:\\data\\file\\tjBank\\天津银行交易明细-有交易对手信息.pdf").getData();
//        System.out.println(json);
//
//        json = tjBankPdfParser.parseTjBankPdfToJson("", "D:\\data\\file\\tjBank\\天津银行-有交易对手信息.pdf").getData();
//        System.out.println(json);

        json = tjBankPdfParser.parseTjBankPdfToJson("", "D:\\data\\file\\tjBank\\天津银行-只有1页.pdf").getData();
        System.out.println(json);
    }
}